/*
 ============================================================================
 Name        : tp1.c
 Author      : Ramiro Diaz
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#define MAX_COORD 500
#define INICIO_DE_DATOS 54  // Segun standard.

typedef struct {
	int ancho, alto;
	void *puntos[ MAX_COORD ][ MAX_COORD ];
} tBitmapData;

void inicializar(tBitmapData *bmp_data) {
	bmp_data->ancho = 0;
	bmp_data->alto = 0;
	for (int i = 0; i < MAX_COORD;i++)
		for (int j = 0;j < MAX_COORD;j++)
			bmp_data->puntos[i][j] = NULL;
}

/*Dados tres bytes (que conforman un pixel) y un color
 determinado indica si hay coincidencia devolviendo un 1*/
int elPixelEsDelColor(int r, int g, int b, unsigned char *pixel) {
	return ((pixel[0] == b) && (pixel[1] == g) && (pixel[2] == r));
}

/*Dada la posicion de memoria de un punto lo asigna como conjunto aislado */
void  hacer(void **punto) {
	*punto = (int**)punto;		// puntero a si mismo
}

/*Retorna el "lider" (representado por un punto) de un conjunto.*/
void* buscar(void **punto) {
	if( *punto != (int**) punto)
		buscar(*punto);
	return *punto;
}

/*Asigna el mismo "lider" a dos conjuntos disjuntos.*/
void  unir(void **puntoUno, void **puntoDos) {
	*puntoDos = *puntoUno;
}

/*Dadas las coordenadas en la matriz devuelve un 1 si se intenta acceder a una
posicion no valida segun el esquema de adyacentes adoptados.*/
int puntoFueraDeMatriz(tBitmapData *bmp_data, int i, int j) {
	if ((j >= bmp_data->ancho)||(i >= bmp_data->alto)||(j < 0))
		return 1;
	return 0;
}

/*Identifica el ancho y largo de la imagen.*/
void procesarImagen(FILE* imagen, tBitmapData *bmp_data) {
	int ancho = 0;
	int alto = 0;
	// Segun el standard el ancho de la imagen se encuentra entre los bytes 18 y 21 inclusive.
	fseek(imagen, 18, SEEK_SET);
	fread(&ancho, sizeof(int), 1, imagen);
	// y el alto de la imagen se encuentra entre los bytes 22 y 25 inclusive.
	fseek(imagen, 22, SEEK_SET);
	fread(&alto, sizeof(int), 1, imagen);
	bmp_data->ancho = ancho;
	bmp_data->alto = alto;
}

/*Analiza la imagen en busqueda de los conjuntos disjuntos.*/
void buscarEnImagen(FILE* imagen, tBitmapData *bmp_data, int r, int g, int b) {
	int padding = 0;
	unsigned char pixel[3];
	for (int i = 0; i < 3;i++)
		pixel[i] = '\0';
	if (((bmp_data->ancho * 3) % 4) != 0)
		padding = 4 - ((bmp_data->ancho * 3) % 4);
	for (int i = 0;i < bmp_data->alto;i++) {
		for (int j = 0;j < bmp_data->ancho;j++) {
			fseek(imagen, INICIO_DE_DATOS + (i*padding) + 3*(j + i*bmp_data->ancho)
					, SEEK_SET);
			fread(pixel, sizeof(char), 3, imagen);
			if (elPixelEsDelColor(r, g, b, pixel))
				hacer(&bmp_data->puntos[i][j]);
			else
				bmp_data->puntos[i][j] = NULL;
			}
	}
}

/*Recorre la matriz uniendo los pixeles que pertenecen a una misma mancha.*/
void procesar(tBitmapData *bmp_data) {
	for (int i = 0; i < bmp_data->alto;i++)
		for (int j = 0;j < bmp_data->ancho;j++)
			if(bmp_data->puntos[i][j]) {
				// Adyacentes de la derecha
				if ((!puntoFueraDeMatriz(bmp_data, i, j+1)) &&
									 (bmp_data->puntos[i][j+1]))
					unir(buscar(&bmp_data->puntos[i][j]), buscar(&bmp_data->puntos[i][j+1]));
				// Los tres adyacentes de abajo.
				// Z es una posicion relativa a j para los tres adyacentes de abajo
				for (int z=-1; z <= 1; z++)
					if ((!puntoFueraDeMatriz(bmp_data, i+1, j + z)) &&
									(bmp_data->puntos[i+1][j + z]))
						unir(buscar(&bmp_data->puntos[i][j]), buscar(&bmp_data->puntos[i+1][j + z]));
			}
}

/*Cuenta la cantidad de manchas.*/
int contarManchas(tBitmapData *bmp_data) {
	int resultado = 0;
	for (int i = 0; i < bmp_data->alto;i++)
		for (int j = 0;j < bmp_data->ancho;j++)
			if (bmp_data->puntos[i][j] == &(bmp_data->puntos[i][j]))
				resultado += 1;
	return resultado;
}

void liberarMemoria(tBitmapData *bmp_data) {
	free(bmp_data);
}

int main(int argc, char *argv[]) {
	tBitmapData *bmp_data = (tBitmapData *) malloc(sizeof(tBitmapData));
	FILE *bmp = fopen(argv[1], "rb");
	inicializar(bmp_data);
	if(!(argc == 5)) {
		liberarMemoria(bmp_data);
		exit(1);
	}
	if(bmp == NULL) {
		liberarMemoria(bmp_data);
		exit(2);
	}
	procesarImagen(bmp, bmp_data);
	buscarEnImagen(bmp, bmp_data, atoi(argv[2]), atoi(argv[3]), atoi(argv[4]));
	procesar(bmp_data);
	printf("BMP (%i x %i) - %i manchas.\n",
		bmp_data->ancho, bmp_data->alto, contarManchas(bmp_data));
	liberarMemoria(bmp_data);
	fclose(bmp);
	exit(0);
}
